import { ref, type Ref } from 'vue'

export interface Config {
  canvas: HTMLCanvasElement
  number: number // 粒子数量
  radius: number // 逃逸鼠标半径(px)
  speend: number // 速度
  color: string // 颜色
  line: number // 连线最大长度
  mouseRadius: number // 鼠标半径(px) 围绕鼠标的圆不会进入粒子
}

function getRangeRandom(min: number, max: number) {
  return Math.random() * (max - min) + min
}
export const useParticle = (config: Config) => {
  const particleManager = new ParticleManager(config)

  return {
    particles: particleManager.particles,
  }
}
// 9个方格
const directions = [
  { x: 0, y: 0 }, // 当前
  { x: 0, y: -1 }, // 上
  { x: -1, y: 0 }, // 左
  { x: 0, y: 1 }, // 下
  { x: 1, y: 0 }, // 右
  { x: -1, y: -1 }, // 左上
  { x: 1, y: -1 }, // 右上
  { x: -1, y: 1 }, // 左下
  { x: 1, y: 1 }, // 右下
]
class ParticleManager {
  #canvas: HTMLCanvasElement
  #ctx: CanvasRenderingContext2D
  #config: Config
  #grid: Array<Array<Particle[]>> // 网格
  #rows: number // 网格行数
  #cols: number // 网格列数
  particles: Particle[]

  #x: Ref<number | null>
  #y: Ref<number | null>
  constructor(config: Config) {
    this.#canvas = config.canvas
    this.#ctx = this.#canvas.getContext('2d') as CanvasRenderingContext2D
    this.#ctx.imageSmoothingEnabled = false // 关闭抗锯齿
    this.#config = config
    this.particles = []
    this.#rows = Math.ceil(this.#canvas.width / this.#config.mouseRadius) + 1
    this.#cols = Math.ceil(this.#canvas.height / this.#config.mouseRadius) + 1
    this.#grid = Array.from({ length: this.#rows }, () =>
      Array(this.#cols)
        .fill(0)
        .map(() => []),
    )
    this.#x = ref(0)
    this.#y = ref(0)
    this.#canvas.addEventListener('mousemove', (e) => {
      ;[this.#x.value, this.#y.value] = [e.offsetX, e.offsetY]
    })
    this.#canvas.addEventListener('mouseleave', () => {
      ;[this.#x.value, this.#y.value] = [null, null]
    })
    this.#drawAll()
    this.#animate()
  }
  #drawAll() {
    const { width, height } = this.#canvas
    for (let i = 0; i < this.#config.number; i++) {
      const [x, y] = [Math.random() * width, Math.random() * height]
      const [gridX, gridY] = [
        Math.max(0, Math.min(this.#rows - 1, Math.floor(x / this.#config.mouseRadius))),
        Math.max(0, Math.min(this.#cols - 1, Math.floor(y / this.#config.mouseRadius))),
      ]
      const particle = new Particle(
        x, // x
        y, // y
        gridX, // 网格坐标
        gridY, // 网格坐标
        Math.random() * 2 * Math.PI, // 角度
        1, // 速度
        {
          radius: this.#config.radius,
          color: this.#config.color,
          speed: this.#config.speend,
          opacity: 1,
          easing: 'easeOutQuad',
          easingFn: (t: number) => {
            return t * (2 - t)
          },
        },
      )
      this.#grid[gridX][gridY].push(particle)
      this.particles.push(particle)
      particle.draw(this.#ctx)
    }
  }
  #animate() {
    const { width, height } = this.#canvas
    // 粒子的自由移动
    this.#ctx.clearRect(0, 0, width, height)
    // 粒子逃逸
    this.#escapeMouse()
    // 连线
    this.particles.forEach((particle) => {
      const { Particle } = particle.update(width, height)
      const { gridX: oldGridX, gridY: oldGridY } = Particle
      particle.gridX = Math.max(
        0,
        Math.min(this.#rows - 1, Math.floor(particle.x / this.#config.mouseRadius)),
      )
      particle.gridY = Math.max(
        0,
        Math.min(this.#cols - 1, Math.floor(particle.y / this.#config.mouseRadius)),
      )
      // 网格坐标变化
      if (oldGridX !== particle.gridX || oldGridY !== particle.gridX) {
        const oldCell = this.#grid[oldGridX][oldGridY]
        const index = oldCell.indexOf(Particle)
        if (index !== -1) oldCell.splice(index, 1)
        const newCell = this.#grid[particle.gridX][particle.gridY]
        newCell.push(Particle)
      }
    })

    this.#connectLine()

    this.particles.forEach((p) => p.draw(this.#ctx))
    requestAnimationFrame(() => this.#animate())
  }
  #connectLine() {
    this.#ctx.strokeStyle = 'rgba(255,255,255,0.1)'
    this.#ctx.beginPath()
    this.#ctx.lineWidth = 0.8
    for (let i = 0; i < this.particles.length; i++) {
      for (let j = i + 1; j < this.particles.length; j++) {
        const p1 = this.particles[i]
        const p2 = this.particles[j]
        const distance = Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2)
        if (distance < this.#config.line) {
          this.#ctx.moveTo(p1.x, p1.y)
          this.#ctx.lineTo(p2.x, p2.y)
          this.#ctx.stroke()
        }
      }
    }
    this.#ctx.closePath()
  }
  #escapeMouse() {
    //TODO: 粒子逃逸鼠标效果
    //TODO: 判断坐标，触发粒子逃避鼠标效果
    // 鼠标不在画布上
    if (this.#x.value === null && this.#y.value === null) return
    // 判断鼠标所在的网格
    const [x, y] = [this.#x.value as number, this.#y.value as number]
    const [gridX, gridY] = [
      Math.max(0, Math.min(this.#rows - 1, Math.floor(x / this.#config.mouseRadius))),
      Math.max(0, Math.min(this.#cols - 1, Math.floor(y / this.#config.mouseRadius))),
    ]
    for (const i in directions) {
      const [X, Y] = [gridX + directions[i].x, gridY + directions[i].y]
      if (X >= 0 && X < this.#rows && Y >= 0 && Y < this.#cols) {
        const cell = this.#grid[X][Y]
        cell.forEach((p) => {
          const distance = Math.sqrt((p.x - x) ** 2 + (p.y - y) ** 2)
          if (distance < this.#config.mouseRadius) {
            p.angle = Math.atan2(p.y - y, p.x - x)
          }
        })
      }
    }
  }
}

interface ParticleConfig {
  radius: number // 半径
  color: string // 颜色
  speed: number // 速度
  opacity: number // 不透明度
  easing: string // 缓动函数
  easingFn: (t: number) => number // 缓动函数
}
class Particle {
  constructor(
    public x: number, // 坐标
    public y: number, // 坐标
    public gridX = 0, // 网格坐标
    public gridY = 0, // 网格坐标
    public angle = 0, // 角度
    public speed = 0, // 速度
    public config: ParticleConfig, // 配置项
  ) {}
  // 计算粒子的自由移动+碰撞检测
  update(width: number, height: number) {
    const direction = getDirection(this.angle)
    // 碰到左侧并且方向向左
    if (this.x < 0 && direction.includes('l')) {
      if (direction == 'l') {
        this.angle = 0
      }
      if (direction == 'ld') {
        this.angle = Math.PI - this.angle
      }
      if (direction == 'lu') {
        this.angle = Math.PI * 3 - this.angle
      }
    }
    // 碰到右侧并且方向向右
    else if (this.x > width && direction.includes('r')) {
      if (direction == 'r') {
        this.angle = Math.PI
      }
      if (direction == 'rd') {
        this.angle = Math.PI - this.angle
      }
      if (direction == 'ru') {
        this.angle = Math.PI * 3 - this.angle
      }
    }
    // 碰到上侧且方向向上
    if (this.y < 0 && direction.includes('u')) {
      if (direction == 'u') {
        this.angle = Math.PI / 2
      }
      if (direction == 'lu') {
        this.angle = Math.PI * 2 - this.angle
      }
      if (direction == 'ru') {
        this.angle = Math.PI * 3 - this.angle
      }
    }
    // 碰到下侧且方向向下
    else if (this.y > height && direction.includes('d')) {
      if (direction == 'd') {
        this.angle = (Math.PI * 3) / 2
      }
      if (direction == 'rd') {
        this.angle = Math.PI * 2 - this.angle
      }
      if (direction == 'ld') {
        this.angle = Math.PI * 2 - this.angle
      }
    }

    this.x += Math.cos(this.angle) * this.config.speed
    this.y += Math.sin(this.angle) * this.config.speed
    return { Particle: this }
  }

  draw(ctx: CanvasRenderingContext2D) {
    ctx.beginPath()
    ctx.arc(this.x, this.y, this.config.radius, 0, 2 * Math.PI)
    ctx.globalAlpha = this.config.opacity
    ctx.fillStyle = this.config.color
    ctx.fill()
  }
}

type direction = 'lu' | 'u' | 'ru' | 'l' | 'r' | 'ld' | 'd' | 'rd' | 'unknown'
const getDirection = (angle: number): direction => {
  if (angle == 0 || angle == Math.PI * 2) return 'r'
  if (angle > 0 && angle < Math.PI / 2) return 'rd'
  if (angle == Math.PI / 2) return 'd'
  if (angle > Math.PI / 2 && angle < Math.PI) return 'ld'
  if (angle == Math.PI) return 'l'
  if (angle > Math.PI && angle < Math.PI * (3 / 2)) return 'lu'
  if (angle == Math.PI * (3 / 2)) return 'u'
  if (angle > Math.PI * (3 / 2) && angle < Math.PI * 2) return 'ru'
  return 'unknown'
}
